Skip to content

Auto offscreen-composite elevated descendants under reduced opacity on Android (flagged) - #58493

Open
crazeface wants to merge 1 commit into
react:mainfrom
crazeface:export-D118581751
Open

Auto offscreen-composite elevated descendants under reduced opacity on Android (flagged)#58493
crazeface wants to merge 1 commit into
react:mainfrom
crazeface:export-D118581751

Conversation

@crazeface

Copy link
Copy Markdown

Summary:
On Android, a view drawn with reduced opacity that contains a descendant with
elevation composites per drawing primitive, so the elevation shadow renders as
concentric bands with an over-opaque center instead of fading uniformly (see
#23090).

Behind the new default-off feature flag
enableAndroidAutoOffscreenCompositingForElevation, ReactViewGroup reports
overlapping rendering (hasOverlappingRendering() returns true) whenever it
contains a descendant with elevation, so the framework bakes the offscreen
decision into the RenderNode at record time. Any alpha applied afterwards --
static opacity, a JS-driven animation, or a native-driver opacity animation
that sets alpha directly on the RenderNode without re-recording -- then
composites the subtree through that layer and the shadow fades uniformly. A layer
is only actually allocated when alpha < 1, so this costs nothing at full
opacity. This is the automatic equivalent of the community
needsOffscreenAlphaCompositing workaround.

The descendant-elevation check is cached and lazily invalidated: it is marked
stale from the logical mount/unmount and elevation hooks -- via a new uimanager
interface ElevatedDescendantCache, never from the subview-clipping/scroll path
-- and rescanned once on the next query. So the per-draw callback stays O(1),
mounting N children stays O(N) rather than O(N^2), and scrolling a
removeClippedSubviews list does no per-frame work. The cache is self-correcting
and cannot drift like a maintained counter. Behavior is unchanged unless the flag
is enabled.

Adds ReactViewGroupTest coverage: flag on/off, alpha == 1 (layer readied ahead of
a later alpha change), no / nested elevated descendant, an elevated view inside a
non-ReactViewGroup container, invalidation bubbling through a non-ReactViewGroup
ancestor, an elevated child added after a prior query, removal, and
needsOffscreenAlphaCompositing.

Changelog: [Internal]

Reviewed By: javache

Differential Revision: D118581751

…n Android (flagged)

Summary:
On Android, a view drawn with reduced `opacity` that contains a descendant with
`elevation` composites per drawing primitive, so the elevation shadow renders as
concentric bands with an over-opaque center instead of fading uniformly (see
react#23090).

Behind the new default-off feature flag
`enableAndroidAutoOffscreenCompositingForElevation`, `ReactViewGroup` reports
overlapping rendering (`hasOverlappingRendering()` returns true) whenever it
contains a descendant with `elevation`, so the framework bakes the offscreen
decision into the RenderNode at record time. Any alpha applied afterwards --
static `opacity`, a JS-driven animation, or a native-driver opacity animation
that sets alpha directly on the RenderNode without re-recording -- then
composites the subtree through that layer and the shadow fades uniformly. A layer
is only actually allocated when `alpha < 1`, so this costs nothing at full
opacity. This is the automatic equivalent of the community
`needsOffscreenAlphaCompositing` workaround.

The descendant-elevation check is cached and lazily invalidated: it is marked
stale from the logical mount/unmount and elevation hooks -- via a new `uimanager`
interface `ElevatedDescendantCache`, never from the subview-clipping/scroll path
-- and rescanned once on the next query. So the per-draw callback stays O(1),
mounting N children stays O(N) rather than O(N^2), and scrolling a
`removeClippedSubviews` list does no per-frame work. The cache is self-correcting
and cannot drift like a maintained counter. Behavior is unchanged unless the flag
is enabled.

Adds ReactViewGroupTest coverage: flag on/off, alpha == 1 (layer readied ahead of
a later alpha change), no / nested elevated descendant, an elevated view inside a
non-ReactViewGroup container, invalidation bubbling through a non-ReactViewGroup
ancestor, an elevated child added after a prior query, removal, and
needsOffscreenAlphaCompositing.

Changelog: [Internal]

Reviewed By: javache

Differential Revision: D118581751
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Sep 12, 2026
@meta-codesync

meta-codesync Bot commented Sep 12, 2026

Copy link
Copy Markdown

@crazeface has exported this pull request. If you are a Meta employee, you can view the originating Diff in D118581751.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. meta-exported p: Facebook Partner: Facebook Partner

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant